package pcapraw

import (
	"net"
	"time"

	"gitee.com/sillyman/simpleUtil/network/ifaceutil"
	"gitee.com/sillyman/simpleUtil/network/macaddr"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"github.com/mdlayher/raw"
	"golang.org/x/net/bpf"
)

type linuxRaw struct {
	rawConn        *raw.Conn
	pcapRecvPktsCh chan gopacket.Packet
	readErrCh      chan error
	closed         bool
}

// New 创建 PcapRaw 实例
func New(localIP net.IP, ethtype uint16, bpfExpr string) (*linuxRaw, error) {
	bpfRawInstructions, err := compileBPFFilter(bpfExpr)
	if err != nil {
		return nil, err
	}
	ifi, err := ifaceutil.GetIfaceByIP(localIP)
	if err != nil {
		return nil, err
	}

	conn, err := raw.ListenPacket(ifi, ethtype, &raw.Config{Filter: bpfRawInstructions})
	if err != nil {
		return nil, err
	}

	lraw := &linuxRaw{rawConn: conn, pcapRecvPktsCh: make(chan gopacket.Packet, 1024)}
	go lraw.packetsToChannel()

	return lraw, nil
}

// ReadGoPkt 读取一个数据包，如果在n秒内没有读取到数据则返回nil
func (l *linuxRaw) ReadGoPkt(timeout time.Duration) (gopacket.Packet, error) {
	select {
	case err := <-l.readErrCh:
		return nil, err
	case <-time.After(timeout):
		return nil, nil
	case pkt := <-l.pcapRecvPktsCh:
		return pkt, nil
	}
}

// WriteLayers
func (l *linuxRaw) WriteLayers(layers []gopacket.SerializableLayer) error {
	data, err := serializeLayers(layers)
	if err != nil {
		return err
	}
	return l.WriteRaw(data)
}

// WriteRaw 向 raw 句柄写入数据
func (l *linuxRaw) WriteRaw(data []byte) error {
	_, err := l.rawConn.WriteTo(data, &raw.Addr{HardwareAddr: macaddr.Zero.HardwareAddr()})
	return err
}

// Close 关闭
func (l *linuxRaw) Close() {
	l.closed = true
	l.rawConn.Close()
}

// packetsToChannel 将 rawConn 的数据读取到 channel
func (l *linuxRaw) packetsToChannel() {
	defer close(l.pcapRecvPktsCh)

	buf := make([]byte, 65535)
	for !l.closed {
		if err := l.rawConn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
			l.readErrCh <- err
			return
		}

		n, _, err := l.rawConn.ReadFrom(buf)
		if err != nil {
			// Immediately retry for temporary network errors
			if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
				continue
			}

			l.readErrCh <- err
			return
		}

		pkt := gopacket.NewPacket(buf[:n], layers.LayerTypeEthernet, gopacket.Default)
		l.pcapRecvPktsCh <- pkt
	}
}

// compileBPFFilter 借助 gopacket/pcap 编译 bpfexpr
func compileBPFFilter(expr string) ([]bpf.RawInstruction, error) {
	pcapBPFInstructions, err := pcap.CompileBPFFilter(layers.LinkTypeEthernet, 1024, expr)
	if err != nil {
		return nil, err
	}

	bpfRawInstructions := make([]bpf.RawInstruction, len(pcapBPFInstructions))
	for _, pcapPbf := range pcapBPFInstructions {
		bpfRawInstructions = append(bpfRawInstructions, bpf.RawInstruction{
			Op: pcapPbf.Code,
			Jt: pcapPbf.Jt,
			Jf: pcapPbf.Jf,
			K:  pcapPbf.K,
		})
	}

	return bpfRawInstructions, nil
}
